/**加载项的类 */
var LoaderItem = function (urls, type, maxRetryTimes) {
    /**加载项列表 */
    this.urls = urls;
    /**加载资源类型 */
    this.type = type;
    /**最大重试次数 */
    this.maxRetryTimes = maxRetryTimes;
    /**当前重试次数 */
    this._currentRetryTimes = 0;
    /**是否已被释放 */
    this.isReleased = false;
    /**所有使用资源的reference url */
    this.resources = {};
};
LoaderItem.prototype = {
    /**
     * 缓存已使用资源
     * @param resource   缓存单个资源的所有使用资源
     */
    _cacheRes(resource) {
        this.resources[cc.loader._getReferenceKey(resource)] = true;
        for (let key of cc.loader.getDependsRecursively(resource)) {
            this.resources[key] = true;
        }
    },

    /**
     * 开始加载资源
     * @param successCall    加载成功回调
     * @param failedCall     加载失败回调
     * @param progressCall   加载进度回调
     */
    load(successCall, failedCall, progressCall) {
        let completedCallFunc = (error, resources) => {
            if (!error) {
                //成功加载，缓存已使用资源，执行成功回调
                this._cacheRes(resources);
                successCall && successCall(resources);
            } else {
                if (this.maxRetryTimes >= this._currentRetryTimes) {
                    failedCall && failedCall(error);
                } else {
                    this._currentRetryTimes++;
                    return this.load(successCall, failedCall, progressCall)
                }
            }
        }
        //加载资源
        cc.loader.loadRes(this.urls, this.type, progressCall, completedCallFunc);
    },

    /**
    * 释放资源
    */
    release() {
        this.isReleased = true;
        let resources = Object.keys(this.resources);
        cc.loader.release(resources);
        this.resources = {};
    },

    /**
    * 释放除了XX以外其他的资源
    * @param otherDepends  其它依赖项，释放资源会跳过这些资源
    */
    releaseWithout(otherDepends) {
        //过滤掉依赖项
        for (let reference in this.resources) {
            if (otherDepends[reference]) {
                delete this.resources[reference];
            }
        }
        //释放其他资源
        this.release();
    }
};

/**加载管理器的类 */
var Loader = function () {
    /**父（根）管理器*/
    this._parentLoader = null;
    /**子管理器数组 */
    this._subLoaders = [];
    /**加载项数组 */
    this._loadItems = [];
    /**是否释放 */
    this._released = false;
};
Loader.prototype = {
    /**
    * 获取到根管理器
    */
    getRootLoader() {
        return this._parentLoader || this;
    },

    /**
     * 获取所有加载的资源
     */
    getAllResources(){
        var allResouces = {};
        //获取自己的引用
        for(let item of this._loadItems) {
            for (var property in item.resources) {
                allResouces[property] = item.resources[property];  
            }
        }
        //获取子管理器的引用
        for (let loader of this._subLoaders) {
            var subAllResouces = loader.getAllResources();
            for (var property in subAllResouces) {
                allResouces[property] = subAllResouces[property];  
            }
        }
        return allResouces;
    },

    /**
     * 创建子管理器
     */
    createSubLoader() {
        let loader = new Loader();
        loader._parentLoader = this;
        this._subLoaders.push(loader);
        return loader;
    },

    /**
     * 移除子管理器
     * @param loader  需移除的子管理器
     */
    _removeSubLoader(loader) {
        let index = this._subLoaders.indexOf(loader);
        if (index >= 0) {
            this._subLoaders.splice(index, 1);
        }
    },


    /**
     * 加载资源
     * @param {string[]|string} urls     加载资源项 必须
     * @param {cc.Asset} type            加载资源类型 必须
     * @param {Function} succCall        加载成功回调 必须
     * @param {Function} failCall        加载失败回调
     * @param {number} retryTimes        重试次数, 默认3次
     * @param {Function} progressCall    加载进度回调
     */
    load(urls, type, succCall, failCall, retryTimes, progressCall) {
        retryTimes = retryTimes || 3;
        let item = new LoaderItem(urls, type, retryTimes);
        let successCall = (res) => {
            if (this._released || item.isReleased) {
                console.error("该资源已经被释放！！");
                // 释放刚加载的资源
                return item.releaseWithout(getRootLoader().getAllResources());
            }
            return succCall && succCall(res);
        };
        let failedCall = (error) => {
            if (this._released) return;
            failCall && failCall(error);
        };
        item.load(successCall, failedCall, progressCall);
        this._loadItems.push(item);
    },


    /**
     * 移除管理器，并释放当前管理器加载的所有资源
     */
    release() {
        this._released = true;
        this._parentLoader._removeSubLoader(this);
        // 释放当前加载的所有资源，需在当前Tick释放，以让后续的加载请求生效
        let allResouces = getRootLoader().getAllResources();
        this._releaseWithout(allResouces);
    },


    /**
     * 释放除了XX以外其他的资源
     * @param allResouces  不能被释放的资源
     */
    _releaseWithout(allResouces) {
        for (let item of this._loadItems) {
            item.releaseWithout(allResouces);
        }
        this._loadItems.length = 0;
        for (let loader of this._subLoaders) {
            loader._releaseWithout(allResouces);
        }
    }

};

/**加载辅助类 */
var LoaderHelper = cc.Class({
    extends: cc.Component,

    init(loader) {
        this._loader = loader;
        return this;
    },

    onDestroy() {
        if (this._loader) {
            this._loader.release();
            this._loader = null;
        }
    }
});

//对外接口相关
/**获取根管理器
 * @returns {Loader}
 */
function getRootLoader() {
    if(!GLoad.rootLoader){
        GLoad.rootLoader = new Loader();
    }
    return GLoad.rootLoader;
};
/**全局资源加载管理 */
var GLoad = {
    /**动态加载预制资源  
     * url 资源路径  
     * successCall 资源加载成功的回调函数，回调函数的参数为预制实例化后的节点    
     * failCall 资源加载失败的回调函数  
     */
    loadPrefab(url, successCall, failCall) {
        var loader = getRootLoader().createSubLoader();
        
        var urls = url;
        var type = cc.Prefab;
        var succCall = (prefab) => {
            var node = cc.instantiate(prefab);
            node.addComponent(LoaderHelper).init(loader);
            successCall && successCall(node);
        };
        var failedCall = (err) => {
            loader.release();
            failCall && failCall(err);
        };
        loader.load(urls, type, succCall, failedCall);
    }
};

window.GLoad = GLoad;

